<?php
/**
 * 盛大糖果盒客户端PHP SDK 
 * 
 * @author    徐鹏程 <xupengcheng@snda.com>
 * @version   0.0.1		2010/05/21
 *
 */

if (!function_exists('curl_init')) {
	throw new Exception('SDO Graph needs the CURL PHP extension.');
}

if (!function_exists('json_decode')) {
	throw new Exception('SDO Graph needs the JSON PHP extension.');
}

require_once ('OAuth.php');

class SDOGraphDataSource {
	public function __construct() {
		session_id() || session_start();
	}

	public function get($key) {
		return isset ($_SESSION['sdograph_data'][$key]) ? $_SESSION['sdograph_data'][$key] : null;
	}

	public function set($key, $value) {
		return $_SESSION['sdograph_data'][$key] = $value;
	}

	public function delete($key) {
		if (isset ($_SESSION['sdograph_data'][$key]))
			unset ($_SESSION['sdograph_data'][$key]);
	}

	public function clear() {
		if (isset ($_SESSION['sdograph_data']))
			unset ($_SESSION['sdograph_data']);
	}
}

class SDOGraph {

	public static $oauth_request_uri = 'http://graph.sdo.com/auth/request';
	public static $oauth_authorize_uri = 'http://graph.sdo.com/auth/authorize';
	public static $oauth_access_uri = 'http://graph.sdo.com/auth/access';
	public static $graph_base = 'http://graph.sdo.com/';

	public static $CURL_OPTS = array (
		CURLOPT_CONNECTTIMEOUT => 3,
		CURLOPT_RETURNTRANSFER => true,
		CURLOPT_TIMEOUT => 3, 			//郑亚东将超时从10秒改为3秒
		CURLOPT_USERAGENT => 'sdo-php-1.1',
		CURLOPT_POST => 1
	);

	protected $_consumer = null;
	protected $_signature = null;

	protected $access_token = null;
	protected $access_secret = null;
    
	protected $callback = null;

	protected $datasource = null;

	public function __construct($config) {
        if (isset($config['consumer_key'], $config['consumer_secret'])) {
	        $this->_consumer = new SdoOAuthConsumer($config['consumer_key'], $config['consumer_secret'], NULL);
	        $this->_signature = new SdoOAuthSignatureMethod_HMAC_SHA1();
	        
	        if (isset ($config['callback'])) {
		        $this->callback = $config['callback'];
            }
	        
	        if (isset ($config['datasource'])) {
		        $this->datasource = $config['datasource'];
	        } else {
		        $this->datasource = new SDOGraphDataSource();
	        }
	        $this->access_token = $this->datasource->get('access_token');
	        $this->access_secret = $this->datasource->get('access_secret');
	        
	        if (!isset ($config['disable_auto_auth'])) {
		        if (isset ($_REQUEST['_graph_callback'])) {
			        $this->getAccessToken();
		        } else {
			        $this->checkAccess();
		        }
	        }
        } else {
			return false;
        	throw new SDOGraphException(array (
                'error_code' => 400,
                'error' => array (
                    'message' => 'invalide configuration',
                    'type' => 'GraphException'
                )
            ));
        }
	}

	/**
	 * 获取Request Token
     * 
     * @var void
     * 
     * @return array, or null if failed
	 */
	public function getReqToken() {
		$req = SdoOAuthRequest::from_consumer_and_token($this->_consumer, null, 'POST', self::$oauth_request_uri, array());
		$req->sign_request($this->_signature, $this->_consumer, null);
        
		$content = $this->_request($req->to_url());
		$content = json_decode($content, true);
        
        return isset($content['oauth_token'], $content['oauth_token_secret']) ? $content : null;
	}

    /**
     * 获取Access Token
     * 
     * @var string $request_token
     * @var string $request_secret
     * 
     * @return array, or null if failed
     */
	public function getAccessToken($request_token=null, $request_secret=null) {
		if ($request_token == null && $request_secret == null) {
            // find token and secret from data store
			$request_token = $this->datasource->get('request_token');
			$request_secret = $this->datasource->get('request_secret');
            
            if ($request_token == null && $request_secret == null) {
                // retrieve the new token and secret from the server
            	$req_token = $this->getReqToken();
                if ($req_token != null) {
                    $request_token = $req_token['oauth_token'];
                    $request_secret = $req_token['oauth_token_secret'];
                } else {
                	return null;
                }
            } else {
            	$this->datasource->delete('request_token');
            	$this->datasource->delete('request_secret');
            }
		}

		$req_token = new SdoOAuthToken($request_token, $request_secret);
		$req = SdoOAuthRequest::from_consumer_and_token($this->_consumer, $req_token, 'POST', self::$oauth_access_uri, array());
		$req->sign_request($this->_signature, $this->_consumer, $req_token);

		$content = $this->_request($req->to_url());
		$content = json_decode($content, true);

        if (isset($content['oauth_token'], $content['oauth_token_secret'])) {
	        $this->datasource->set('access_token', $content['oauth_token']);
	        $this->datasource->set('access_secret', $content['oauth_token_secret']);
	        
	        $this->access_token = $content['oauth_token'];
	        $this->access_secret = $content['oauth_token_secret'];
	        
	        return $content;
        }
        
        return null;
	}
    
    /**
     * 获取Login URL
     * 
     * @var string $callback_url
     * @var array $req_token
     * 
     * @return string, or null if failed
     */
    public function getLoginURL($callback_url, $req_token=null) {
        if (empty($req_token)) {
            $req_token = $this->getReqToken();
        }
        
        if (isset($req_token['oauth_token'], $req_token['oauth_token_secret'])) {
	        $this->datasource->set('request_token', $req_token['oauth_token']);
	        $this->datasource->set('request_secret', $req_token['oauth_token_secret']);
	        
	        // 请求授权
	        return self::$oauth_authorize_uri . "?oauth_token={$req_token['oauth_token']}&oauth_callback=" . urlencode($callback_url);
        }
        
	    return null;
    }

    /**
     * 验证是否授权
     * 
     * @var void
     * 
     * @return boolean
     */
	public function isAuthed() {
        return $this->access_token && $this->access_secret;
	}

	/**
	 * 检查是否授权
     * 
     * @var void
     * 
     * @return mixed
	 */
	public function checkAccess() {
		if (!isset ($_GET['usertoken'])) {
			if ($this->isAuthed()) {
				return true;
            }
		} else {
			$user_token = $_GET['usertoken'];
			if ($user_token === $this->datasource->get('user_token')) {
				if ($this->isAuthed()) {
					return true;
                }
			}
            
			$this->datasource->set('user_token', $user_token);
		}

		if ($this->callback) {
			$callback = $this->callback;
		} else {
			$callback = 'http://' . $_SERVER['SERVER_NAME'];
            
			if ($_SERVER['SERVER_PORT'] && $_SERVER['SERVER_PORT'] != '80') {
				$callback .= ':' . $_SERVER['SERVER_PORT'];
            }
            
            if (isset($_SERVER['REQUEST_URI'])) {
	            $callback .= $_SERVER['REQUEST_URI'];
            }
		}
        
		if (strpos($callback, '?'))
			$callback .= '&_graph_callback=1';
		else
			$callback .= '?_graph_callback=1';

		$req_url = $this->getLoginURL($callback);
        if ($req_url) {
	        if(!headers_sent()){
		        header('HTTP/1.1 303 See Other', true);
		        header('Content-Type:text/html; charset=utf-8', true);
		        header("Location: {$req_url}", true);
	        }else{
		        echo '<html><head><meta http-equiv="refresh" content="0;URL=', $req_url, '" /><title>303 See Other ...</title></head><body></body></htm>';
	        }
	        exit;
        } else {
			return false;
        	throw new SDOGraphException(array (
                'error_code' => 400,
                'error' => array (
                    'message' => 'invalide login url',
                    'type' => 'GraphException'
                )
            ));
        }
	}

	/**
	 * 注销认证
     * 
     * @var void
     * 
     * @return void
	 */
	public function logout() {
		if ($this->isAuthed()) {
            // sign out
			try {
				$this->api('auth/destory');
			} catch (SDOGraphException $e) {
                // TODO
			}
		}
        
		$this->datasource->clear();
	}

	public function api($api, $params=array(), $method='GET') {
		$url = self::$graph_base . $api;
		$token = new SdoOAuthToken($this->access_token, $this->access_secret);

        if (!isset($params['method'])) {
	        $params['method'] = $method;
        }
        
		$req = SdoOAuthRequest::from_consumer_and_token($this->_consumer, $token, 'POST', $url, $params);
		$req->sign_request($this->_signature, $this->_consumer, $token);

		$content = $this->_request($req->to_url(), $params);
		$ret = json_decode($content, true);
		if ($ret === null && ($content !== '' && $content !== 'null' && $content !== 'NULL')) {
			return false;
			throw new SDOGraphException(array (
				'error_code' => 400,
				'error' => array (
					'message' => 'bad response',
					'type' => 'CurlException',
					
				),
				
			));
		}
        
		return $ret;
	}

	protected function _request($url, $params = array ()) {
		$ch = curl_init();
		$opts = self::$CURL_OPTS;
		$opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&');
		$opts[CURLOPT_URL] = $url;
		curl_setopt_array($ch, $opts);
		$result = curl_exec($ch);
		$info = curl_getinfo($ch);
		if ($result === false) {
			$e = new SDOGraphException(array (
				'error_code' => curl_errno($ch),
				'error' => array (
					'message' => curl_error($ch),
					'type' => 'CurlException',
				),
			));
			curl_close($ch);
			return false;
			throw $e;
		}

		if (200 != $info['http_code']) {
			$e = new SDOGraphException(array (
				'error_code' => $info['http_code'],
				'error' => array (
					'message' => 'bad request',
					'type' => 'CurlException',
				),
			));
			curl_close($ch);
			return false;
			throw $e;
		}

		curl_close($ch);
		return $result;
	}
}

class SDOGraphException extends Exception {
	/**
	 * The result from the API server that represents the exception information.
	 */
	protected $result;

	/**
	 * Make a new API Exception with the given result.
	 *
	 * @param Array $result the result from the API server
	 */
	public function __construct($result) {
		$this->result = $result;

		$code = isset ($result['error_code']) ? $result['error_code'] : 0;
		$msg = isset ($result['error']) ? $result['error']['message'] : $result['error_msg'];
		parent :: __construct($msg, $code);
	}

	/**
	 * Return the associated result object returned by the API server.
	 *
	 * @returns Array the result from the API server
	 */
	public function getResult() {
		return $this->result;
	}

	/**
	 * Returns the associated type for the error. This will default to
	 * 'Exception' when a type is not available.
	 *
	 * @return String
	 */
	public function getType() {
		return isset ($this->result['error']) && isset ($this->result['error']['type']) ? $this->result['error']['type'] : 'Exception';
	}

	/**
	 * To make debugging easier.
	 *
	 * @returns String the string representation of the error
	 */
	public function __toString() {
		$str = $this->getType() . ': ';
		if ($this->code != 0) {
			$str .= $this->code . ': ';
		}
		return $str . $this->message;
	}
}